using System;

using System.Collections.Generic;
using System.Xml;
using System.Collections;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;


namespace Pharmacy.Ultilities
{
	/// <summary>
	/// Summary description for XmlHelper.
	/// </summary>
	public class XmlHelper
	{	
		/// <summary>
		/// Query XML
		/// </summary>
		/// <param name="xDoc">Document</param>
		/// <param name="xPath">Path of node</param>
		/// <returns></returns>
		public static XmlNodeList Query(XmlDocument xDoc, string xPath)
		{
			return xDoc.SelectNodes(xPath);
		}


		/// <summary>
		/// Get attribute value of XmlNode
		/// </summary>
		/// <param name="node">XML Node</param>
		/// <param name="attName">Attribute Name</param>
		/// <returns>string node Attribute</returns>
		public static string GetAttributeValue(XmlNode node, string attName)
		{
			if(node == null || node.Attributes[attName] == null) 
			{
				return null;
			}

			return node.Attributes[attName].Value;
		}


		/// <summary>
		/// Build a hastable contains childnodes' name as key, and values are the flat index of childnodes. 
		/// Note that if the childnode of mapNode has any child, this child will be ignored. 
		/// </summary>
		/// <param name="mapNode">XmlNode contains childnodes</param>
		/// <param name="nameOfAttribue">Name of the attribute of node. Ex: "flat"</param>
		/// <param name="ignoreEmptyAttribue">ChildNode will be ignored if 
		/// ChildNode.Attributes[nameOfAttribue]).InnerText.Trim() == string.Empty</param>
		/// <returns>Return a hashtable</returns>
		// hungle @dicvn 2007 01 25: create
		public static Hashtable GetNodeAttribute(XmlNode mapNode, string nameOfAttribue, 
			bool ignoreEmptyAttribue)
		{
			Hashtable properties = new Hashtable();
			GetAttributes(properties, mapNode, nameOfAttribue, ignoreEmptyAttribue, 
				true, "inparentable");
			return properties;
		}


        /// <summary>
        /// Need to update: if you are the creator of this method, please update the comment for it.
        /// </summary>
        /// <param name="mapNode"></param>
        /// <returns></returns>
        public static SortedDictionary<string, int> GetNodeAttribute(XmlNode mapNode, string flatAtt,
            string columnAtt, bool allowInParentTable) {
            SortedDictionary<string, int> properties = new SortedDictionary<string, int>();

            string xpath;

            try {
                XmlNodeList nodeList = mapNode.SelectNodes(string.Format("//node()[@{0}]", flatAtt));

                foreach (XmlNode node in nodeList) {
                    XmlAttributeCollection attrColl = node.Attributes;
                    int column = -1;

                    if (!int.TryParse(attrColl[flatAtt].Value, out column) || column < 0) continue;

                    xpath = GetRelativePath(node, columnAtt, allowInParentTable);

                    if (!string.IsNullOrEmpty(xpath) && !properties.ContainsKey(xpath)) {
                        properties.Add(xpath, column);
                    }
                }
            }
            catch (Exception ex) {
                throw ex;
            }

            return properties;
        }

        /// <summary>
        /// Gets relative path of an XmlNode base on value of an attribute.
        /// </summary>
        /// <param name="node">An XmlNode</param>
        /// <param name="relativeAtt">The attribute to get path</param>
        /// <param name="allowInParentTableAtt">If the specified attribute of a node is not exist,
        /// check if that node has att "inparenttable='true'". If it has, skips it and go to it's parent.</param>
        /// <returns></returns>
        public static string GetRelativePath(XmlNode node, string relativeAtt, bool allowInParentTableAtt) {
            if (node == null || string.IsNullOrEmpty(relativeAtt)) return null;

            const string VAL_INPARENTTABLEATT = "inparenttable";
            const string VAL_TRUE = "true";
            XmlNode cursor = node;
            string ret = string.Empty;

            while (cursor != null && cursor != node.OwnerDocument.DocumentElement) {
                //ret = string.Format("/{0}{1}", cursor.Name, ret);
                string inParentTableAtt = GetAttributeValue(cursor, VAL_INPARENTTABLEATT);

                if (cursor.HasChildNodes && allowInParentTableAtt && !string.IsNullOrEmpty(inParentTableAtt) &&
                    inParentTableAtt.ToLower() == VAL_TRUE) {
                    //skips this node then goes to parent node
                }
                else {
                    XmlNamedNodeMap nodeMap = cursor.Attributes;
                    if (nodeMap.GetNamedItem(relativeAtt) == null) {
                        return null;
                    }
                    else {
                        ret = string.Format(".{0}{1}", nodeMap.GetNamedItem(relativeAtt).InnerText, ret);
                    }
                }

                cursor = cursor.ParentNode;
            }

            if (ret != null && ret.IndexOf('.') == 0) ret = ret.Remove(0, 1);

            return ret;
        }

		/// <summary>
		/// Build a hastable contains childnodes' name as key, and values are the flat index of childnodes. 
		/// Note that if the childnode of mapNode has any child, this child will be ignored. 
		/// </summary>
		/// <param name="mapNode">XmlNode contains childnodes</param>
		/// <param name="nameOfAttribue">Name of the attribute of node. Ex: "flat"</param>
		/// <param name="ignoreEmptyAttribue">ChildNode will be ignored if 
		/// ChildNode.Attributes[nameOfAttribue]).InnerText.Trim() == string.Empty</param>
		/// <param name="ignoreInParentTableNode"></param>
		/// <param name="inParentTableAttName"></param>
		/// <returns>Return a hashtable</returns>
		// hungle @dicvn 2007 01 25: create
		public static Hashtable GetNodeAttribute(XmlNode mapNode, string nameOfAttribue, 
			bool ignoreEmptyAttribue, bool ignoreInParentTableNode, 
			string inParentTableAttName)
		{
			Hashtable properties = new Hashtable();
			GetAttributes(properties, mapNode, nameOfAttribue, ignoreEmptyAttribue, 
				ignoreInParentTableNode, inParentTableAttName);
			return properties;
		}

		/// <summary>
		/// Get Attritures
		/// </summary>
		/// <param name="properties">properties</param>
		/// <param name="mapNode">mapNode</param>
		/// <param name="nameOfAttribue">Name of Attribute</param>
		/// <param name="ignoreEmptyAttribue">Ignore empty Attribute</param>
		/// <param name="ignoreInParentTableNode">Ignore Parent tableNode</param>
		/// <param name="inParentTableAttName">inParentTableAttName</param>
		private static void GetAttributes(Hashtable properties, XmlNode mapNode, 
			string nameOfAttribue, bool ignoreEmptyAttribue, bool ignoreInParentTableNode, 
			string inParentTableAttName)
		{
			if(mapNode == null) return; 
			foreach(XmlNode field in mapNode.ChildNodes)//in fields)//
			{		
				if(field.HasChildNodes)
				{
					if(!ignoreInParentTableNode && field.Attributes[inParentTableAttName] != null && 
						field.Attributes[inParentTableAttName].Value.Trim() == "true")
					{
						GetAttributes(properties, field, nameOfAttribue, ignoreEmptyAttribue, 
							ignoreInParentTableNode, inParentTableAttName);
					}
					continue;
				}

				if(field.Attributes[nameOfAttribue] == null || 
					((field.Attributes[nameOfAttribue]).InnerText.Trim() == string.Empty && 
					ignoreEmptyAttribue))
				{
					continue;
				}

				properties.Add(field.Name, field.Attributes[nameOfAttribue].InnerText.Trim());
			}
		}

		/// <summary>
		/// Get Attribute
		/// </summary>
		/// <param name="properties">Properties Input</param>
		/// <param name="mapNode">Map Node</param>
		/// <param name="nameOfNameAttribue">Name of NameAttribute</param>
		/// <param name="nameOfValueAttribue">Name of ValueAttribute</param>
		/// <param name="ignoreEmptyAttribue">IgnoreEmptyAttribute</param>
		/// <param name="ignoreInParentTableNode">IgnoreInparentTableNode</param>
		/// <param name="inParentTableAttName">InParrentTableAttName</param>
		private static void GetAttributes(Hashtable properties, XmlNode mapNode, 
			string nameOfNameAttribue, string nameOfValueAttribue, 
			bool ignoreEmptyAttribue, bool ignoreInParentTableNode, 
			string inParentTableAttName)
		{
			if(mapNode == null) return;

			foreach(XmlNode field in mapNode.ChildNodes)//in fields)//
			{		
				if(field.HasChildNodes)
				{
					if(!ignoreInParentTableNode && field.Attributes[inParentTableAttName] != null && 
						field.Attributes[inParentTableAttName].Value.Trim() == "true")
					{
						GetAttributes(properties, field, nameOfNameAttribue, nameOfValueAttribue, 
							ignoreEmptyAttribue, ignoreInParentTableNode, inParentTableAttName);
					}
					continue;
				}

				if(field.Attributes[nameOfValueAttribue] == null || 
					((field.Attributes[nameOfValueAttribue]).InnerText.Trim() == string.Empty && 
					ignoreEmptyAttribue))
				{
					continue;
				}

				properties.Add(field.Name, field.Attributes[nameOfValueAttribue].InnerText.Trim());
			}
		}


		//Tacle @dicvn/20070310 Create
		/// <summary>
		/// GetAttributeInfo
		/// </summary>
		/// <param name="properties">Properties</param>
		/// <param name="mapNode">Xml Node</param>
		/// <param name="attributeName">Attribute Name</param>
		public static void GetAttributeInfo(Hashtable properties, XmlNode mapNode,string attributeName)
		{
			try
			{
				if (mapNode.HasChildNodes==true )
				{
					foreach(XmlNode field in mapNode.ChildNodes)
					{					
						GetAttributeInfo(properties,field,attributeName);
					}
				}
				else
				{
					if (mapNode.Attributes[attributeName]!=null )
					{
						string C = mapNode.Name.Trim().ToString();   
						string F = mapNode.Attributes[attributeName].InnerText.Trim().ToString();  
						if (!properties.Contains(C))
							properties.Add(C,F);
					}
				}
			}
			catch(Exception ex)
			{
				throw new Exception(ex.Message.ToString());   
			}
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public object XmlFileToObject(string filePath, Type type)
        {
            XmlSerializer xmlSerializer;
            FileStream fileStream = null;
            try
            {
                xmlSerializer = new XmlSerializer(type);
                fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
                object objectFromXml = xmlSerializer.Deserialize(fileStream);
                return objectFromXml;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (fileStream != null) fileStream.Close();
            }
        }

        public void ObjectToXmlFile(Object objToXml, string filePath)
        {
            StreamWriter stWriter = null;
            XmlSerializer xmlSerializer;
            try
            {
                xmlSerializer = new XmlSerializer(objToXml.GetType());

                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                    stWriter = new StreamWriter(filePath);
                }
                else
                {
                    stWriter = new StreamWriter(filePath);
                }

                System.Xml.Serialization.XmlSerializerNamespaces xs = new XmlSerializerNamespaces();
                //To remove namespace and any other inline information tag
                xs.Add(string.Empty, string.Empty);
                xmlSerializer.Serialize(stWriter, objToXml, xs);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (stWriter != null) stWriter.Close();
            }
        }

        /// <summary>
        /// Gets the value by processing given xpath.
        /// </summary>
        /// <param name="xmlNode">Reference to an xml node.</param>
        /// <param name="xpath">A set of syntax rules for defining parts of an XML document.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.08.09 - ThangTran: added.
        /// </history>
        public static string Select(XmlNode xmlNode, string xpath)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(xmlNode != null);
            Debug.Assert(xpath != null);
            // --- End parameters checking code -------------------------------            

            // get target node
            XmlNode node = xpath != string.Empty
                               ? xmlNode.SelectSingleNode(xpath)
                           // if the xpath is empty, use given node            
                               : xmlNode;
            if (node == null)
            {
                // node not found
                return null;
            }

            // get node value
            return node.InnerText;
        }

        /// <summary>
        /// Gets the value by processing given xpath or use default value.
        /// </summary>
        /// <param name="xmlNode">Reference to an xml node.</param>
        /// <param name="xpath">A set of syntax rules for defining parts of an XML document.</param>
        /// <param name="defaultValue">Default value will be used if the query is not success.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.08.09 - ThangTran: added.
        /// </history>
        public static T SelectOrUseDefaultValue<T>(XmlNode xmlNode, string xpath, T defaultValue)
        {
            // try to get node value
            string value = Select(xmlNode, xpath);

            // if it is null, use default value
            if (value == null)
            {
                return defaultValue;
            }

            // try to convert value
            try
            {
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch (Exception ex)
            {
                // just catch some type of exceptions
                if (ex is InvalidCastException || ex is FormatException || ex is OverflowException || ex is ArgumentException)
                {
                    // use default value
                    return defaultValue;
                }

                // else: do not catch this
                throw;
            }
        }

        /// <summary>
        /// Gets path of given node.
        /// </summary>
        /// <remarks>
        /// Credit to: Jon Skeet, Robert Rossney, James Randle.
        /// URL: http://stackoverflow.com/questions/241238/how-to-get-xpath-from-an-xmlnode-instance-c
        /// </remarks>
        public static string GetNodePath(XmlNode node)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(node != null);
            // --- End parameters checking code -------------------------------

            // check if the node is Attribute
            if (node.NodeType == XmlNodeType.Attribute)
            {
                // attributes have an OwnerElement, not a ParentNode; also they have
                // to be matched by name, not found by position
                return String.Format(
                    "{0}/@{1}",
                    GetNodePath(((XmlAttribute)node).OwnerElement),
                    node.Name
                    );
            }

            // check if the node is root
            if (node.ParentNode == null)
            {
                // the only node with no parent is the root node, which has no path
                return "";
            }

            //get the index
            int nodeIndex = 1;
            XmlNode xmlNode = node;

            // try to move to the first node
            while (xmlNode.PreviousSibling != null)
            {
                // increase node index
                nodeIndex++; 
                xmlNode = xmlNode.PreviousSibling;
            }

            // the path to a node is the path to its parent, plus "/node()[n]", where 
            // n is its position among its siblings.
            return String.Format(
                "{0}/node()[{1}]",
                GetNodePath(node.ParentNode),
                nodeIndex
                );
        }
	}
}
